﻿#include "redis_db_connection.h"

#include "hiredis/hiredis.h"
#include "fmt.h"
#include "log.h"

#include <sstream>
#include <memory>

using namespace std;

#ifdef WIN32
#ifdef _DEBUG
#pragma comment(lib,"libsasld.lib")
#pragma comment(lib,"libeay32.lib")
#pragma comment(lib,"ssleay32.lib")
#else
#pragma comment(lib,"libsasl.lib")
#pragma comment(lib,"libeay32.lib")
#pragma comment(lib,"ssleay32.lib")
#endif
#endif

const char LOG_NAME[] = "CRedisDbConnection";
CRedisDbConnection::CRedisDbConnection()
{
}

CRedisDbConnection::~CRedisDbConnection()
{

}

bool CRedisDbConnection::Connect(const std::string & host
	, const std::string & port_or_socket
	, const std::string & user
	, const std::string & password
	, const std::string & database)
{


	struct timeval timeout = { 10, 500000 }; // 1.5 seconds
	int port = 0;
	port = atoi(port_or_socket.c_str());

	m_pRedisContext = redisConnect(host.c_str(), port);
	if (m_pRedisContext == nullptr || m_pRedisContext->err)
	{
		if (m_pRedisContext)
		{
			LOG_FATAL("Connection error: " << m_pRedisContext->errstr);
			redisFree(m_pRedisContext);
		}
		else
		{
			LOG_FATAL("Connection error: can't allocate redis context");
		}
		return false;
	}

	//auth
	{
		std::basic_ostringstream<char, std::char_traits<char>> stream;
		stream << "AUTH " << password;
		struct redisReply* pRedisReply = (struct redisReply*) redisCommand(m_pRedisContext, stream.str().c_str());

		if (NULL != pRedisReply)
		{
			std::string  result = pRedisReply->str;
			if (pRedisReply->type == REDIS_REPLY_STATUS && result == "OK")
			{
				freeReplyObject(pRedisReply);
			}
			else
			{
				LOG_FATAL("Auth Failed: " << m_pRedisContext->errstr);
				freeReplyObject(pRedisReply);
				redisFree(m_pRedisContext);
				return false;
			}
		}
		else
		{
			LOG_FATAL("Auth Failed ");
		}
	}

	//select database
	{
		std::basic_ostringstream<char, std::char_traits<char>> datastream;
		datastream << "SELECT  " << database;
		struct redisReply* pRedisreply = (struct redisReply*) redisCommand(m_pRedisContext, datastream.str().c_str());

		if (NULL != pRedisreply)
		{
			std::string  result = pRedisreply->str;
			if (pRedisreply->type == REDIS_REPLY_STATUS && result == "OK")
			{
				freeReplyObject(pRedisreply);
			}
			else
			{
				LOG_FATAL("Database not exit: " << m_pRedisContext->errstr);
				freeReplyObject(pRedisreply);
				redisFree(m_pRedisContext);
				return false;
			}
		}
		else
		{
			LOG_FATAL("Database not exit: " << datastream.str());
		}
	}

	m_bConnected = true;
	LOG_INFO("Database Connect  OK" << host);

	return true;
}

RedisQueryResultSptr CRedisDbConnection::Query(const std::string& commandstring)
{
	LOG_INFO(commandstring);
	struct redisReply* pRedisreply = (struct redisReply*) redisCommand(m_pRedisContext, commandstring.c_str());

	if (pRedisreply != nullptr)
	{
		if (pRedisreply->type == REDIS_REPLY_ERROR)
		{
			LOG_ERROR("Redis Error : " << pRedisreply->str);
			return 0;
		}
	}
	else
	{
		LOG_ERROR(commandstring << " empty reply");
		LOG_ERROR("errorID: " << m_pRedisContext->err << ", errorString: " << m_pRedisContext->errstr);

		return  0;
	}

//	CRedisQueryResult *queryResult = Mem_New CRedisQueryResult(redisreply);
	auto queryResult = std::make_shared<CRedisQueryResult>(pRedisreply);

	return queryResult;

}

RedisQueryResultSptr CRedisDbConnection::Query(const char *format, ...)
{

	va_list ap;
	void *reply = NULL;
	va_start(ap, format);
	reply = redisvCommand(m_pRedisContext, format, ap);
	va_end(ap);

	struct redisReply* redisreply = (struct redisReply*) reply;

	//CRedisQueryResult *queryResult = Mem_New CRedisQueryResult(redisreply);
	auto queryResult = std::make_shared<CRedisQueryResult>(redisreply);

	return queryResult;
}

RedisQueryResultSptr CRedisDbConnection::Query(const std::string& commandstring, const std::string& param1)
{
	struct redisReply* redisreply = (struct redisReply*) redisCommand(m_pRedisContext, commandstring.c_str(), param1.c_str());

	if (redisreply)
	{
		if (redisreply->type == REDIS_REPLY_ERROR)
		{
			LOG_ERROR("Redis Error : " << redisreply->str);
			return 0;
		}
	}
	else
	{
		return  0;
	}

//	RedisQueryResult *queryResult = Mem_New RedisQueryResult(redisreply);

	auto queryResult = std::make_shared<CRedisQueryResult>(redisreply);

	return queryResult;
}

void CRedisDbConnection::KeepAlive(int iS)
{
	if (!m_bConnected)
		return;

	time_t curTime = time(0);
	bool bPing = false;
	{
		//todo: add lock
		if (curTime - m_iLastActiveTime > iS)
		{
			m_iLastActiveTime = curTime;
			bPing = true;
		}
	}

	if (bPing)
		Ping();
}

void CRedisDbConnection::Ping()
{
	struct redisReply* redisreply = (struct redisReply*) redisCommand(m_pRedisContext, "PING");

	if (redisreply)
	{
		if (!(redisreply->type == REDIS_REPLY_STATUS  && redisreply->str == "PONG"))
		{
			LOG_ERROR("Redis Ping Error");
		}
		freeReplyObject(redisreply);
	}
	else
	{
		LOG_ERROR("Redis Ping Error redisreply empty");
		LOG_ERROR("errorID: " << m_pRedisContext->err << ", errorString: " << m_pRedisContext->errstr);
	}
}